package com.itdct.onflow.core.extend

import cn.hutool.core.util.ReflectUtil
import cn.hutool.core.util.StrUtil
import com.baomidou.mybatisplus.annotation.TableField
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper
import com.baomidou.mybatisplus.core.enums.SqlMethod
import com.baomidou.mybatisplus.core.toolkit.Constants
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper
import com.baomidou.mybatisplus.extension.toolkit.SqlRunner
import com.itdct.onflow.core.constant.CommonString
import com.itdct.onflow.core.constant.ReturnCode
import com.itdct.onflow.core.context.ContextHolder
import com.itdct.onflow.core.dto.BaseLoginDTO
import com.itdct.onflow.core.entity.BaseEntity
import com.itdct.onflow.core.entity.BaseUserEntity
import com.itdct.onflow.core.entity.IdEntity
import com.itdct.onflow.core.exception.CommonException
import com.itdct.onflow.core.mapper.BaseMapper
import com.itdct.onflow.core.util.TimeUtil
import org.apache.ibatis.binding.MapperMethod.ParamMap
import org.apache.ibatis.logging.LogFactory
import org.apache.ibatis.session.SqlSession
import org.springframework.aop.framework.AopProxyUtils
import org.springframework.aop.support.AopUtils
import java.lang.reflect.Field
import java.lang.reflect.Modifier
import java.util.*
import java.util.stream.Collectors

/**
 * @author Zhouwx
 * @date 2024/4/9 18:25:24
 * @version 1.0
 * @description
 */
const val DEFAULT_BATCH_SIZE = 100000

/**
 * 通过Id查找对象，如果找不到会抛异常
 * 如果不需要抛异常，请使用selectById方法
 */
fun <T : IdEntity, M : BaseMapper<T>> M.selectByIdEx(id: Long?, notFoundMessage: String = "无法找到ID为：${id}的对象"): T {
    if (id == null) {
        throw CommonException(CommonString.REQUIRE_ID)
    }
    val t = this.selectById(id)
    if (t == null) {
        throw CommonException(notFoundMessage)
    }
    if (t is BaseEntity) {
        if (t.del != 0L) {
            throw CommonException(notFoundMessage)
        }
    }
    return t
}

fun <T : IdEntity, M : BaseMapper<T>> M.selectByIdsEx(ids: List<Long?>): MutableList<T> {
    val filter = ids.filterNotNull()
    if (filter.isEmpty()) {
        return mutableListOf()
    }
    return this.selectByIds(filter)
}

fun <T : IdEntity, M : BaseMapper<T>> M.selectAll(): MutableList<T> {
    return this.selectList(QueryWrapper())
}

fun <T : IdEntity, M : BaseMapper<T>> M.insertEx(entity: T, userId: Long? = getUserId(), setIdNull: Boolean = true): Long {
    insertNecessaryField(entity, userId, setIdNull)
    this.insert(entity)
    return entity.id!!
}

fun <T : IdEntity, M : BaseMapper<T>> M.updateEx(entity: T, userId: Long? = getUserId()): Long {
    updateNecessaryField(entity, userId)
    this.updateById(entity)
    return entity.id!!
}

fun <T : IdEntity, M : BaseMapper<T>> M.saveEx(entity: T, userId: Long? = getUserId(), setIdNull: Boolean = true): Long {
    entity.id?.also {
        updateEx(entity, userId)
    } ?: run {
        insertEx(entity, userId, setIdNull)
    }
    return entity.id!!
}

fun <T : IdEntity, M : BaseMapper<T>> M.deleteByIdEx(id: Long, userId: Long? = getUserId()) {
    val t = this.selectById(id)
    if (t == null) {
        throw CommonException(ReturnCode.DELETE_ERROR, CommonString.ENTITY_NOT_FOUND)
    }
    if (t is BaseEntity) {
        if (t.del != 0L) {
            throw CommonException(ReturnCode.DELETE_ERROR, CommonString.ENTITY_NOT_FOUND)
        }
    }

    if (t is BaseEntity) {
        deleteNecessaryField(t, userId)
        this.updateById(t)
    }
    this.deleteById(id)
}

fun <T : IdEntity, M : BaseMapper<T>> M.deleteEx(entity: T, userId: Long? = getUserId()) {
    entity.id?.also {
        deleteByIdEx(it, userId)
    } ?: run {
        throw CommonException(ReturnCode.DELETE_ERROR, CommonString.ENTITY_NOT_FOUND)
    }
}

fun <T : IdEntity, M : BaseMapper<T>> M.deleteBatchEx(deleteEntities: List<T>, userId: Long? = getUserId()) {
    if (deleteEntities.size == 0) {
        return
    }
    for (deleteEntity in deleteEntities) {
        deleteNecessaryField(deleteEntity, userId)
    }

    this.saveBatch(deleteEntities, userId)
    this.deleteByIds(deleteEntities.map { it.id })
}

fun <T : IdEntity, M : BaseMapper<T>> M.saveBatch(entityList: List<T>, userId: Long? = getUserId(), batchSize: Int = DEFAULT_BATCH_SIZE, setIdNull: Boolean = true) {
    if (entityList.size == 0) {
        return
    }
    val log = LogFactory.getLog(BaseMapper::class.java)
    val t = entityList[0]
    val mapperClass = getNoProxyMapperClass(this)
    SqlHelper.saveOrUpdateBatch<T>(t::class.java, mapperClass, log, entityList, batchSize, { sqlSession: SqlSession?, entity: T? ->
        // INFO: DCTANT: 2021/12/27 insert判断，返回true则是走insert代码，返回false则会走后面的update代码
        if (entity == null) {
            return@saveOrUpdateBatch false
        }
        val id = entity.id
        if (id == null) {
            // INFO: DCTANT: 2021/12/27 insert前加一些自己必要的业务逻辑，如setCreateTime、setDel、setVersion等等
            insertNecessaryField(entity, userId, setIdNull)
            return@saveOrUpdateBatch true
        } else {
            // INFO: DCTANT: 2021/12/27 去执行update的代码
            return@saveOrUpdateBatch false
        }
    }, { sqlSession: SqlSession, entity: T? ->
        // INFO: DCTANT: 2021/12/27 判断为update，然后执行必要操作
        if (entity == null) {
            return@saveOrUpdateBatch
        }
        // INFO: DCTANT: 2021/12/27 update前加一些自己的业务逻辑，如setUpdateTime等等
        updateNecessaryField(entity, userId)
        val param = ParamMap<T>()
        // INFO: DCTANT: 2022/8/22 参数需要为Constants.ENTITY，也就是里面的et，否则会报et这个参数无法找到
        param[Constants.ENTITY] = entity
        val sqlStatement = SqlHelper.getSqlStatement(mapperClass, SqlMethod.UPDATE_BY_ID)
        sqlSession.update(sqlStatement, param)
    })
}

fun <T : IdEntity, M : BaseMapper<T>> M.updateBatchWithNativeSql(updateList: List<T>, userId: Long? = getUserId(), ignoreTransientFields: Boolean = true, vararg excludeFields: String) {
    if (updateList.size == 0) {
        return
    }
    val t = updateList[0]
    val tableNameAnnotation = t::class.java.getAnnotation(TableName::class.java)
    val tableName = tableNameAnnotation.value
    val fields = ReflectUtil.getFields(t.javaClass)
    val stringBuilder = kotlin.text.StringBuilder()
    stringBuilder.append("UPDATE ").append(tableName).append(" SET \n")
    var index = 0
    for (field in fields) {
        val fieldName = field.name
        var isExclude = false
        var isTransient = false

        if (ignoreTransientFields) {
            // INFO: DCTANT: 2022/9/13 如果忽略transient修饰的字段，则需要判断字段是否含有transient，如果含有了，则需要排除
            val modifiers = field.modifiers
            isTransient = Modifier.isTransient(modifiers)
        }

        if (excludeFields.contains(fieldName)) {
            isExclude = true
        }

        if (isExclude || isTransient) {
            // INFO: DCTANT: 2022/9/13 被排除的或者用transient修饰的字段则直接跳过
            index++
            continue
        }

        val type = field.type
        var isBooleanColumn = false
        var isDate = false
        if (type.simpleName.equals("Boolean", ignoreCase = true)) {
            // INFO: DCTANT: 2022/9/7 boolean类型需要特殊处理
            isBooleanColumn = true
        }

        if (type.simpleName.equals("Date", ignoreCase = true)) {
            isDate = true
        }

        val columnName = getColumnName(field)
        stringBuilder.append("\t").append(columnName).append(" = (\n\tCASE \n")
        for (item in updateList) {
            val id = item.id
            updateNecessaryField(item, userId)
            val value = getFieldValue(item, field, isBooleanColumn, isDate)
            stringBuilder.append("\t\tWHEN id = ").append(id).append(" THEN ").append(value).append("\n")
        }
        if (index == fields.size - 1) {
            stringBuilder.append("\tEND)\n")
        } else {
            stringBuilder.append("\tEND),\n")
        }
        index++
    }
    val ids = updateList.stream().map { item: T -> item.id.toString() + "" }.collect(Collectors.toList())
    val idsString = java.lang.String.join(",", ids)
    stringBuilder.append("WHERE id IN(").append(idsString).append(");")
    val sql = stringBuilder.toString()
    this.nativeSql(sql)
}

fun <T : IdEntity, M : BaseMapper<T>> M.insertBatchWithNativeSql(
    insertList: List<T>,
    userId: Long? = getUserId(),
    setIdNull: Boolean = true,
    ignoreTransientFields: Boolean = true,
    vararg excludeFields: String
) {
    if (insertList.size == 0) {
        return
    }
    val t = insertList[0]
    val tableNameAnnotation = t::class.java.getAnnotation(TableName::class.java)
    val tableName = tableNameAnnotation.value
    val fields = ReflectUtil.getFields(t.javaClass)
    val stringBuilder = kotlin.text.StringBuilder()
    stringBuilder.append("INSERT INTO ").append(tableName).append(" (")

    val usingFields = ArrayList<Field>()
    for (field in fields) {
        val fieldName = field.name
        var isExclude = false
        var isTransient = false
        if (ignoreTransientFields) {
            // INFO: DCTANT: 2022/9/13 如果忽略transient修饰的字段，则需要判断字段是否含有transient，如果含有了，则需要排除
            val modifiers = field.modifiers
            isTransient = Modifier.isTransient(modifiers)
        }

        if (isTransient) {
            continue
        }

        for (exceptField in excludeFields) {
            if (fieldName == exceptField) {
                isExclude = true
            }
        }

        if (isExclude) {
            continue
        }

        if (setIdNull) {
            // INFO: DCTANT: 2022/9/14 带TableId注解的字段直接排除
            val tableId = field.getAnnotation(TableId::class.java)
            if (tableId != null) {
                continue
            }
        }
        usingFields.add(field)
    }

    for (i in usingFields.indices) {
        val field = usingFields[i]
        if (i != 0) {
            stringBuilder.append(", ")
        }
        val columnName = getColumnName(field)
        stringBuilder.append(columnName)
    }
    stringBuilder.append(") VALUES \n")

    for (i in insertList.indices) {
        val item: T = insertList.get(i)
        if (i != 0) {
            stringBuilder.append(", \n")
        }
        insertNecessaryField(item, userId, setIdNull)
        for (fieldIndex in usingFields.indices) {
            if (fieldIndex != 0) {
                stringBuilder.append(", ")
            } else {
                stringBuilder.append("(")
            }
            val field = usingFields[fieldIndex]
            val type = field.type
            var isBooleanColumn = false
            var isDate = false
            if (type.simpleName.equals("Boolean", ignoreCase = true)) {
                // INFO: DCTANT: 2022/9/7 boolean类型需要特殊处理
                isBooleanColumn = true
            }
            if (type.simpleName.equals("Date", ignoreCase = true)) {
                isDate = true
            }
            val value = getFieldValue(item, field, isBooleanColumn, isDate)
            stringBuilder.append(value)
        }
        stringBuilder.append(")")
    }
    stringBuilder.append(";")
    val sql = stringBuilder.toString()
    this.nativeSql(sql)
}

private fun getColumnName(field: Field): String {
    val fieldName = field.name
    val columnName: String
    val tableField = field.getAnnotation(TableField::class.java)
    columnName = tableField?.value ?: StrUtil.toUnderlineCase(fieldName)
    return columnName
}

private fun <T : IdEntity?> getFieldValue(item: T, field: Field, isBooleanColumn: Boolean, isDate: Boolean): String {
    val fieldValue = ReflectUtil.getFieldValue(item, field)
    var value: String
    if (fieldValue == null) {
        value = "NULL"
    } else {
        value = fieldValue.toString()
        // INFO: DCTANT: 2022/9/7 解决可能字段值中出现引号的问题
        value = value.replace("'".toRegex(), "\\\\'")
        if (isBooleanColumn) {
            value = if (value.equals("true", ignoreCase = true)) {
                "1"
            } else {
                "0"
            }
        } else if (isDate) {
            val date = fieldValue as Date
            value = ("'" + TimeUtil.dateToTimeString(date, TimeUtil.yyyyMMddHHmmss_HYPHEN)).toString() + "'"
        } else {
            value = "'$fieldValue'"
        }
    }
    return value
}

fun <T : IdEntity, R : BaseMapper<T>> getNoProxyMapperClass(mapper: R): Class<R>? {
    var mapperClass: Class<R>? = null

    val classes = AopProxyUtils.proxiedUserInterfaces(mapper)
    mapperClass = if (classes.size > 0) {
        classes[0] as Class<R>
    } else {
        AopUtils.getTargetClass(mapper) as Class<R>
    }
    return mapperClass
}

private fun <T : IdEntity, M : BaseMapper<T>> M.insertNecessaryField(entity: T, userId: Long? = getUserId(), setIdNull: Boolean = true) {
    if (setIdNull) {
        entity.id = null
    }
    if (entity.createTime == null) {
        entity.createTime = Date()
    }

    if (entity.updateTime == null) {
        entity.updateTime = Date()
    }

    if (entity is BaseEntity) {
        entity.version = 0

        if (entity is BaseUserEntity) {
            userId?.also {
                entity.createUser = it
                entity.updateUser = it
            }
        }
    }
}

private fun <T : IdEntity, M : BaseMapper<T>> M.updateNecessaryField(entity: T, userId: Long? = getUserId()) {
    entity.updateTime = Date()

    if (entity is BaseEntity) {
        if (entity is BaseUserEntity) {
            userId?.also {
                entity.updateUser = it
            }
        }
    }
}

private fun <T : IdEntity, M : BaseMapper<T>> M.deleteNecessaryField(entity: T, userId: Long? = getUserId()) {
    if (entity is BaseEntity) {
        entity.deleteTime = Date()

        if (entity is BaseUserEntity) {
            userId?.also {
                entity.deleteUser = it
            }
        }
    }
}

private fun <T : IdEntity, M : BaseMapper<T>> M.getUserId(): Long? {
    ContextHolder.getContext<Any, BaseLoginDTO<Any>>()?.accountId?.also {
        return it
    }
    return null
}

fun nativeSelectCount(sql: String?, vararg params: Any?): Long {
    val count = SqlRunner.db().selectCount(sql, *params)
    return count
}

fun nativeSelectList(sql: String?, vararg params: Any?): List<Map<String, Any>> {
    val maps = SqlRunner.db().selectList(sql, *params)
    return maps
}

fun nativeSelectOne(sql: String?, vararg params: Any?): Map<String, Any> {
    val map = SqlRunner.db().selectOne(sql, *params)
    return map
}


